package com.xinggq.pay.service.interceptor;

import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.xinggq.pay.service.context.AppIdContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.JdbcType;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 应用ID SQL拦截器
 * 自动为支持多租户的表添加app_id查询条件
 *
 * @author xinggq
 * @since 2025/1/10
 */
@Slf4j
public class AppIdSqlInterceptor implements InnerInterceptor {

  /**
   * 用于匹配 WHERE 子句的正则表达式（忽略大小写）
   */
  private static final Pattern WHERE_PATTERN = Pattern.compile("\\bwhere\\b", Pattern.CASE_INSENSITIVE);

  private static final Pattern END_PATTERN =  Pattern.compile("\\s+(order\\s+by|group\\s+by|having|limit)\\s+", Pattern.CASE_INSENSITIVE);
  private static final String SELECT = "select";
  private static final String UPDATE = "update";
  private static final String DELETE = "delete";

  /**
   * 需要进行应用隔离的表名列表
   */
  private static final List<String> TENANT_TABLES = Arrays.asList(
      "hqt_pay_order",
      "hqt_channel_interaction_record",
      "hqt_yse_pay_config",
      "hqt_channel_callback",
      "hqt_app_callback_config",
      "hqt_callback_retry_log"
  );

  /**
   * 应用ID字段名
   */
  private static final String APP_ID_COLUMN = "app_id";

  @Override
  public void beforeQuery(Executor executor, MappedStatement ms, Object parameter,
      RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    Long appId = AppIdContext.getAppId();
    if (appId == null) {
      log.debug("当前线程未设置应用ID，跳过SQL拦截");
      return;
    }

    try {
      String originalSql = boundSql.getSql();
      String modifiedSql = addAppIdConditionToSql(originalSql);

      if (!originalSql.equals(modifiedSql)) {
        PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
        mpBoundSql.sql(modifiedSql);
        addAppIdParameter(ms.getConfiguration(), boundSql, appId);
        log.debug("SQL已添加应用ID条件 [appId={}]: {}", appId, modifiedSql);
      }
    } catch (Exception e) {
      log.warn("添加应用ID条件失败，使用原始SQL: {}", e.getMessage());
    }
  }

  @Override
  public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) {
    Long appId = AppIdContext.getAppId();
    if (appId == null) {
      log.debug("当前线程未设置应用ID，跳过SQL拦截");
      return;
    }

    try {
      BoundSql boundSql = ms.getBoundSql(parameter);
      String originalSql = boundSql.getSql();
      String modifiedSql = addAppIdConditionToSql(originalSql);

      if (!originalSql.equals(modifiedSql)) {
        PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
        mpBoundSql.sql(modifiedSql);
        addAppIdParameter(ms.getConfiguration(), boundSql, appId);
        log.debug("UPDATE SQL已添加应用ID条件 [appId={}]: {}", appId, modifiedSql);
      }
    } catch (Exception e) {
      log.warn("为UPDATE添加应用ID条件失败，使用原始SQL: {}", e.getMessage());
    }
  }

  /**
   * 为SQL语句添加应用ID条件
   */
  private String addAppIdConditionToSql(String sql) {
    if (!containsTenantTable(sql)) {
      return sql;
    }

    String lowerSql = sql.toLowerCase().trim();

    // 处理SELECT语句
    if (lowerSql.startsWith(SELECT)) {
      return addAppIdToSelect(sql);
    }
    // 处理UPDATE语句
    else if (lowerSql.startsWith(UPDATE)) {
      return addAppIdToUpdate(sql);
    }
    // 处理DELETE语句
    else if (lowerSql.startsWith(DELETE)) {
      return addAppIdToDelete(sql);
    }

    return sql;
  }

  /**
   * 为SELECT语句添加应用ID条件
   */
  private String addAppIdToSelect(String sql) {
    return appendAppIdConditionAtWhereEnd(sql);
  }

  /**
   * 为UPDATE语句添加应用ID条件
   */
  private String addAppIdToUpdate(String sql) {
    return appendAppIdConditionAtWhereEnd(sql);
  }

  /**
   * 为DELETE语句添加应用ID条件
   */
  private String addAppIdToDelete(String sql) {
    return appendAppIdConditionAtWhereEnd(sql);
  }

  /**
   * 在 WHERE 子句的末尾追加 app_id 条件，如果不存在 WHERE，则在合适位置插入新的 WHERE
   */
  private String appendAppIdConditionAtWhereEnd(String sql) {
    Matcher whereMatcher = WHERE_PATTERN.matcher(sql);
    if (whereMatcher.find()) {
      // 存在 WHERE：在 WHERE 条件末尾（ORDER BY/GROUP BY/HAVING/LIMIT 之前）追加
      int whereEnd = whereMatcher.end();
      Matcher endMatcher = END_PATTERN.matcher(sql);
      endMatcher.region(whereEnd, sql.length());
      int insertPos = sql.length();
      if (endMatcher.find()) {
        insertPos = endMatcher.start();
      }
      return sql.substring(0, insertPos) + " AND app_id = ? " + sql.substring(insertPos);
    } else {
      // 不存在 WHERE：在结束子句前插入新的 WHERE app_id 条件
      Matcher endMatcher = END_PATTERN.matcher(sql);
      if (endMatcher.find()) {
        return sql.substring(0, endMatcher.start()) + " WHERE app_id = ? " + sql.substring(endMatcher.start());
      } else {
        return sql + " WHERE app_id = ?";
      }
    }
  }

  /**
   * 检查SQL是否包含需要隔离的表
   */
  private boolean containsTenantTable(String sql) {
    String lowerSql = sql.toLowerCase();
    return TENANT_TABLES.stream().anyMatch(lowerSql::contains);
  }

  /**
   * 添加appId参数到BoundSql中
   */
  private void addAppIdParameter(Configuration configuration, BoundSql boundSql, Long appId) {
    List<ParameterMapping> parameterMappings = new ArrayList<>(boundSql.getParameterMappings());
    parameterMappings.add(
        new ParameterMapping.Builder(configuration, APP_ID_COLUMN, Long.class)
            .jdbcType(JdbcType.BIGINT)
            .build()
    );

    PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
    mpBoundSql.parameterMappings(parameterMappings);

    // 添加参数值
    boundSql.setAdditionalParameter(APP_ID_COLUMN, appId);
  }
}